package com.pine.common.database.interceptor;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.pine.common.database.annotations.SensitiveEntity;
import com.pine.common.database.annotations.SensitiveField;
import com.pine.common.util.sensitive.SensitiveUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 加密拦截器
 *
 * @author pine manage
 * @since 2024-10-09
 */
@Slf4j
@Data
public class MybatisPlusSensitiveInterceptor implements InnerInterceptor {

    /**
     * 密文密钥
     */
    private static String sensitiveTextKey;

    /**
     * 密文前缀
     */
    private static String sensitiveTextPrefix;

    private static final Map<Class<?>, Map<String, Field>> CLASS_FIELD_MAP = new HashMap<>(16);

    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {
        Class<?> entityClass = parameter.getClass();
        // 实体类上标注SensitiveEntity注解时，进行处理
        if (AnnotationUtil.hasAnnotation(entityClass, SensitiveEntity.class)) {
            Map<String, Field> fieldMap = CLASS_FIELD_MAP.get(entityClass);
            if (Objects.nonNull(fieldMap)) {
                // 遍历所有需要加密的字段，执行加密操作
                fieldMap.forEach((fieldName, field) -> encryptField(field, parameter));
                return;
            } else {
                fieldMap = new HashMap<>(8);
                CLASS_FIELD_MAP.put(entityClass, fieldMap);
            }

            SensitiveEntity sensitiveEntity = entityClass.getAnnotation(SensitiveEntity.class);

            // 获取需要处理的字段名
            String[] fieldNames = sensitiveEntity.fieldNames();
            SensitiveField[] sensitiveFields = sensitiveEntity.value();
            Set<String> fieldNameSet = new HashSet<>(Arrays.asList(fieldNames));
            for (SensitiveField sensitiveField : sensitiveFields) {
                fieldNameSet.add(sensitiveField.fieldName());
            }

            // 扫描所有变量，如果存在SensitiveField注解，则添加到fieldNameSet中
            if (sensitiveEntity.scanFields()) {
                Field[] declaredFields = entityClass.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    if (AnnotationUtil.hasAnnotation(declaredField, SensitiveField.class)) {
                        SensitiveField sensitiveField = declaredField.getAnnotation(SensitiveField.class);
                        fieldNameSet.add(sensitiveField.fieldName());
                        fieldMap.put(sensitiveField.fieldName(), declaredField);
                    }
                }
            }

            if (!fieldNameSet.isEmpty()) {
                for (String fieldName : fieldNameSet) {
                    if (!fieldMap.containsKey(fieldName)) {
                        getField(entityClass, fieldName, fieldMap);
                    }
                }
            }

            // 遍历所有需要加密的字段，执行加密操作
            fieldMap.forEach((fieldName, field) -> encryptField(field, parameter));
        }
    }

    private Field getField(Class<?> entityClass, String fieldName, Map<String, Field> fieldMap) {
        try {
            Field declaredField = entityClass.getDeclaredField(fieldName);
            fieldMap.put(fieldName, declaredField);
            return declaredField;
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 加密字段
     *
     * @param field     字段对象
     * @param parameter 字段所属对象
     */
    private void encryptField(Field field, Object parameter) {
        field.setAccessible(true);
        Object fieldValue;
        try {
            // 获取字段原值
            fieldValue = field.get(parameter);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        if (ObjectUtil.isNotEmpty(fieldValue)) {
            String fieldValueString = fieldValue.toString();
            // 依据密文的前缀确定是否已经加密过
            if (isEncrypted(fieldValueString)) {
                return;
            }
            ReflectionUtils.setField(field, parameter, generateEncryptStr(fieldValue));
        }
    }

    private Object generateEncryptStr(Object fieldValue) {
        if (ObjectUtil.isEmpty(fieldValue)) {
            return fieldValue;
        }

        String fieldValueString = fieldValue.toString();
        byte[] fixedKey = SensitiveUtil.getFixedKey(sensitiveTextKey);
        fieldValue = SensitiveUtil.aesEncrypt(fieldValueString, fixedKey);
        return sensitiveTextPrefix + fieldValue;
    }

    /**
     * 判断字段是否已经加密过
     *
     * @param fieldValue 字段值
     * @return 是否已经加密过
     */
    private boolean isEncrypted(Object fieldValue) {
        if (ObjectUtil.isEmpty(fieldValue)) {
            return true;
        }

        String fieldValueString = fieldValue.toString();
        return fieldValueString.startsWith(sensitiveTextPrefix);
    }


    public static void setSensitiveTextKey(String sensitiveTextKey) {
        Objects.requireNonNull(sensitiveTextKey);
        MybatisPlusSensitiveInterceptor.sensitiveTextKey = sensitiveTextKey;
    }

    public static String getSensitiveTextKey() {
        Objects.requireNonNull(sensitiveTextKey);
        return MybatisPlusSensitiveInterceptor.sensitiveTextKey;
    }

    public static void setSensitiveTextPrefix(String sensitiveTextPrefix) {
        Objects.requireNonNull(sensitiveTextPrefix);
        MybatisPlusSensitiveInterceptor.sensitiveTextPrefix = sensitiveTextPrefix;
    }

    public static String getSensitiveTextPrefix() {
        Objects.requireNonNull(sensitiveTextPrefix);
        return MybatisPlusSensitiveInterceptor.sensitiveTextPrefix;
    }
}
